using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace XTengine.ComponentModel
{
    public class ComponentManager
    {
        public static readonly Dictionary<string, Assembly> ComponentAssemblyMap = new Dictionary<string, Assembly>();
        internal static readonly Dictionary<Type, Type> ComponentTypeMap = new Dictionary<Type, Type>();
        internal static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> ComponentPropertyMap = new Dictionary<Type, Dictionary<string, PropertyInfo>>();

        private ResourcePool<XTengineObject> entityResourcePool = new ResourcePool<XTengineObject>(EntityInitializer);

        private static void EntityInitializer(XTengineObject newEntity)
        {
        }

        public XTengineObject CreateEntity()
        {
            XTengineObject entity = entityResourcePool.New();

            return entity;
        }

        public void RemoveEntity(XTengineObject entity)
        {
            entityResourcePool.Free(entity);

            for (int i = 0; i < entity.Components.Count; i++)
            {
                foreach (KeyValuePair<Type, IXTengineComponent> pair in entity.Components)
                    pair.Value.Detach();
            }

            entity.Components.Clear();
        }

        private void ComponentsFromAssembly(Assembly assembly)
        {
            Type[] exportedTypes;

            try
            {
                exportedTypes = assembly.GetTypes();
            }
            catch
            {
                return;
            }

            for (int i = 0; i < exportedTypes.Length; i++)
            {
                Type type = exportedTypes[i];

                if (type.IsGenericType)
                    continue;

                Type xtengineComponent = typeof(XTengineComponent);

                object[] componentAttributes = type.GetCustomAttributes(xtengineComponent, false);

                if (componentAttributes == null || componentAttributes.Length == 0)
                    continue;

                XTengineComponent componentDefinition = componentAttributes.First() as XTengineComponent;

                if (componentDefinition == null)
                    continue;

                // Get all interfaces, and then filter them for non IXTengineComponent interfaces that implement it, and take the first
                Type baseInterface = null;

                foreach (Type interf in type.GetInterfaces())
                {
                    if (interf.GetInterface("IXTengineComponent", false) != null)
                    {
                        baseInterface = interf;
                        break;
                    }
                }

                if (baseInterface == null)
                    continue;
#if DEBUG
                string desiredName = "I" + type.Name;
                string requiredName = desiredName.ToLower();


                System.Diagnostics.Debug.Assert(baseInterface.Name.ToLower() == requiredName, "Component " + type.Name + " should implement " + desiredName
                                                + " but implements " + baseInterface.Name);
#endif
                // Get all members, and then filter them for properties
                MemberInfo[] members = baseInterface.GetMembers();

                Dictionary<string, PropertyInfo> componentProperties = new Dictionary<string, PropertyInfo>();

                foreach (PropertyInfo property in members.OfType<PropertyInfo>())
                    componentProperties[property.Name.ToLower()] = property;

                ComponentPropertyMap[componentDefinition.InterfaceType] = componentProperties;

                componentDefinition.ClassType = type;

                ComponentTypeMap[componentDefinition.InterfaceType] = type;

                ComponentAssemblyMap[componentDefinition.InterfaceType.Name] = assembly;
            }
        }

        public ComponentManager(Assembly[] componentAssemblies)
        {
            Assembly executingAssembly = Assembly.GetCallingAssembly();

            if (componentAssemblies != null)
            {
                foreach (Assembly assembly in componentAssemblies)
                    ComponentsFromAssembly(assembly);
            }

            ComponentsFromAssembly(executingAssembly);
        }
    }
}
